//
// Created by songpingwang on 25-7-18.
//

#ifndef CHARSET_UTILS_H
#define CHARSET_UTILS_H
#include <string>
#include <vector>
#include <stdexcept>

#ifdef _WIN32
#include <windows.h>
#else
#include <iconv.h>
#endif

namespace CharsetUtils {
    inline bool is_utf8(const std::string& str) {
        const auto* bytes = reinterpret_cast<const unsigned char*>(str.c_str());
        const size_t len = str.length();
        const unsigned char* p = bytes;
        const unsigned char* const end = bytes + len;

        while (p < end) {
            uint32_t code_point;
            size_t num_bytes;

            if (*p < 0x80) {                              // 0xxxxxxx (ASCII) 1 byte
                code_point = *p;
                num_bytes = 1;
            } else if ((*p & 0xE0) == 0xC0) {             // 110xxxxx 10xxxxxx 2 bytes
                if (p + 1 >= end || (p[1] & 0xC0) != 0x80) return false;
                code_point = (p[0] & 0x1F) << 6 | p[1] & 0x3F;
                num_bytes = 2;
                if (code_point < 0x80) return false;      // Overlong encoding
            } else if ((*p & 0xF0) == 0xE0) {             // 1110xxxx 10xxxxxx 10xxxxxx 3 bytes
                if (p + 2 >= end || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) return false;
                code_point = (p[0] & 0x0F) << 12 | (p[1] & 0x3F) << 6 | p[2] & 0x3F;
                num_bytes = 3;
                if (code_point < 0x800) return false;      // Overlong encoding
                if (code_point >= 0xD800 && code_point <= 0xDFFF) return false;
            } else if ((*p & 0xF8) == 0xF0) {              // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  4 bytes
                if (p + 3 >= end || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80) return false;
                code_point = (p[0] & 0x07) << 18 | (p[1] & 0x3F) << 12 | (p[2] & 0x3F) << 6 | p[3] & 0x3F;
                num_bytes = 4;
                if (code_point < 0x10000) return false;   // Overlong encoding
                if (code_point > 0x10FFFF) return false;
            } else {
                return false;
            }
            p += num_bytes;
        }
        return p == end;
    }


    inline std::string gbk_to_utf8(const std::string &str_gbk) {
        if (str_gbk.empty()) {
            return "";
        }

#ifdef _WIN32
    const int gbk_codepage = 936;
    const int gbk_len = static_cast<int>(str_gbk.length());

    // 1: GBK -> UTF-16 (Wide Character)
    int wide_len = MultiByteToWideChar(gbk_codepage, 0, str_gbk.c_str(), gbk_len, NULL, 0);
    if (wide_len == 0) {
        throw std::runtime_error("MultiByteToWideChar failed to get length for GBK to UTF-16.");
    }
    std::vector<wchar_t> wide_buffer(wide_len);
    MultiByteToWideChar(gbk_codepage, 0, str_gbk.c_str(), gbk_len, wide_buffer.data(), wide_len);

    // 2: UTF-16 -> UTF-8
    int utf8_len = WideCharToMultiByte(CP_UTF8, 0, wide_buffer.data(), wide_len, NULL, 0, NULL, NULL);
    if (utf8_len == 0) {
        throw std::runtime_error("WideCharToMultiByte failed to get length for UTF-16 to UTF-8 conversion.");
    }
    std::vector<char> utf8_buffer(utf8_len);
    WideCharToMultiByte(CP_UTF8, 0, wide_buffer.data(), wide_len, utf8_buffer.data(), utf8_len, NULL, NULL);

    return std::string(utf8_buffer.data());
#else
        const iconv_t cd = iconv_open("UTF-8", "GBK");
        if (cd == reinterpret_cast<iconv_t>(-1)) {
            throw std::runtime_error("iconv_open failed: cannot convert from GBK to UTF-8.");
        }

        auto in_buf = const_cast<char *>(str_gbk.c_str());
        size_t in_len = str_gbk.length();
        const size_t out_len_initial = in_len * 3 + 1;
        std::vector<char> out_buffer(out_len_initial);
        char *out_buf = out_buffer.data();
        size_t out_len = out_len_initial;

        if (iconv(cd, &in_buf, &in_len, &out_buf, &out_len) == static_cast<size_t>(-1)) {
            iconv_close(cd);
            throw std::runtime_error("iconv conversion from GBK to UTF-8 failed.");
        }

        std::string result(out_buffer.data(), out_len_initial - out_len);
        iconv_close(cd);
        return result;
#endif
    }
} // namespace CharsetUtils

#endif //CHARSET_UTILS_H
